﻿#region License

// Copyright (c) 2011, Matt Holmes
// All rights reserved.
// 
// Redistribution and use in source and binary forms, with or without modification, are permitted provided 
// that the following conditions are met:
// 
// * Redistributions of source code must retain the above copyright notice, this list of conditions and the 
//   following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and
//   the following disclaimer in the documentation and/or other materials provided with the distribution.
// 
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED 
// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 
// PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 
// TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 
// OF THE POSSIBILITY OF SUCH DAMAGE.

#endregion

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Windows;
using System.Xml.Serialization;
using IniParser;
using Microsoft.Practices.Prism.ViewModel;

namespace SkyrimTweaker.Common {
	public class SkyrimValue<T> : NotificationObject {
		#region Fields

		private T _value;

		#endregion

		#region Properties

		public string Description { get; set; }

		public T Value {
			get { return _value; }
			set {
				_value = value;
				RaisePropertyChanged ("Value");
			}
		}

		#endregion
	}

	public class SkyrimData : NotificationObject, IWeakEventListener {
		#region Fields

		private readonly List<IniValueInfo> _iniValues = new List<IniValueInfo> ();
		private readonly Dictionary<string, object> _values = new Dictionary<string, object> ();
		private IniData _iniData;
		private bool _isDirty;
		private List<TweakInfo> _tweakInfo;

		#endregion

		#region Constructors

		public SkyrimData () {
			LoadTweakDescriptions ();
			CacheIniValues ();
		}

		#endregion

		#region Properties

		[IniValue (Section = "SaveGame", Name = "fAutosaveEveryXMins")]
		public SkyrimValue<float> AutoSaveTime {
			get { return GetValueContainer<float> ("fAutosaveEveryXMins"); }
		}

		[IniValue (Section = "Imagespace", Name = "bDoDepthOfField")]
		public SkyrimValue<bool> DepthOfField {
			get { return GetValueContainer<bool> ("bDoDepthOfField"); }
		}

		[IniValue (Section = "Water", Name = "bForceHighDetailReflections")]
		public SkyrimValue<bool> ForceHighDetailReflections {
			get { return GetValueContainer<bool> ("bForceHighDetailReflections"); }
		}

		public bool IsDirty {
			get { return _isDirty; }
			set {
				_isDirty = value;
				RaisePropertyChanged ("IsDirty");
			}
		}

		[IniValue (Section = "Display", Name = "bDrawLandShadows")]
		public SkyrimValue<bool> LandShadows {
			get { return GetValueContainer<bool> ("bDrawLandShadows"); }
		}

		[IniValue (Section = "Controls", Name = "bMouseAcceleration")]
		public SkyrimValue<bool> MouseAcceleration {
			get { return GetValueContainer<bool> ("bMouseAcceleration"); }
		}

		[IniValue (Section = "Particles", Name = "iMaxDesired")]
		public SkyrimValue<int> ParticleDensity {
			get { return GetValueContainer<int> ("iMaxDesired"); }
		}

		[IniValue (Section = "Water", Name = "bReflectExplosions")]
		public SkyrimValue<bool> ReflectExplosions {
			get { return GetValueContainer<bool> ("bReflectExplosions"); }
		}

		[IniValue (Section = "Interface", Name = "bShowCompass")]
		public SkyrimValue<bool> ShowCompass {
			get { return GetValueContainer<bool> ("bShowCompass"); }
		}

		[IniValue (Section = "GamePlay", Name = "bShowFloatingQuestMarkers")]
		public SkyrimValue<bool> ShowFloatingQuestMarkers {
			get { return GetValueContainer<bool> ("bShowFloatingQuestMarkers"); }
		}

		[IniValue (Section = "GamePlay", Name = "bShowQuestMarkers")]
		public SkyrimValue<bool> ShowQuestMarkers {
			get { return GetValueContainer<bool> ("bShowQuestMarkers"); }
		}

		[IniValue (Section = "Display", Name = "bTreesReceiveShadows")]
		public SkyrimValue<bool> TreeShadows {
			get { return GetValueContainer<bool> ("bTreesReceiveShadows"); }
		}

		[IniValue (Section = "Display", Name = "iPresentInterval")]
		public SkyrimValue<bool> VerticalSync {
			get { return GetValueContainer<bool> ("iPresentInterval"); }
		}

		[IniValue (Section = "Water", Name = "bAutoWaterSilhouetteReflections")]
		public SkyrimValue<bool> WaterAutoSilhouetteReflection {
			get { return GetValueContainer<bool> ("bAutoWaterSilhouetteReflections"); }
		}

		[IniValue (Section = "Water", Name = "iWaterBlurAmount")]
		public SkyrimValue<int> WaterBlurAmount {
			get { return GetValueContainer<int> ("iWaterBlurAmount"); }
		}

		[IniValue (Section = "Water", Name = "iWaterReflectHeight")]
		public SkyrimValue<int> WaterReflectTextureHeight {
			get { return GetValueContainer<int> ("iWaterReflectHeight"); }
		}

		[IniValue (Section = "Water", Name = "iWaterReflectWidth")]
		public SkyrimValue<int> WaterReflectTextureWidth {
			get { return GetValueContainer<int> ("iWaterReflectWidth"); }
		}

		[IniValue (Section = "Water", Name = "bUseWaterReflectionBlur")]
		public SkyrimValue<bool> WaterReflectionBlur {
			get { return GetValueContainer<bool> ("bUseWaterReflectionBlur"); }
		}

		#endregion

		#region Public Methods

		public TValue GetValue<TValue> (string name) {
			var container = GetValueContainer<TValue> (name);
			if (container == null) {
				return default (TValue);
			}

			return container.Value;
		}

		public void ReadIniData (IniData data) {
			_iniData = data;
			ParseIniValues ();
			IsDirty = false;
		}

		public void WriteIniData () {
			MergeIniValues ();
			IsDirty = false;
		}

		#endregion

		#region Private Methods

		private void CacheIniValues () {
			var query = from p in GetType ().GetProperties (BindingFlags.Public | BindingFlags.Instance)
			            let attrs = p.GetCustomAttributes (typeof (IniValueAttribute), false).Cast<IniValueAttribute> ().ToArray ()
			            where
			            	attrs.Length != 0 &&
			            	p.PropertyType.IsGenericType &&
			            	p.PropertyType.GetGenericTypeDefinition () == typeof (SkyrimValue<>)
			            select
			            	new IniValueInfo {
			            		Property = p,
			            		Type = p.PropertyType.GetGenericArguments ()[0],
			            		Attribute = attrs[0]
			            	};

			_iniValues.AddRange (query);
		}

		private void CheckSection (string name) {
			var section = _iniData.Sections[name];
			if (section == null) {
				_iniData.Sections.AddSection (name);
			}
		}

		private string GetDescription (string id) {
			var info = _tweakInfo.FirstOrDefault (t => t.Id == id);
			if (info == null) {
				return id;
			}

			return info.Description;
		}

		private SkyrimValue<TValue> GetValueContainer<TValue> (string name) {
			if (_values.ContainsKey (name)) {
				return _values[name] as SkyrimValue<TValue>;
			}

			SetValueContainer (name, MakeValue (default (TValue), GetDescription (name)));
			return _values[name] as SkyrimValue<TValue>;
		}

		private void LoadTweakDescriptions () {
			try {
				using (var stream = GetType ().Assembly.GetManifestResourceStream ("SkyrimTweaker.Common.Data.TweakDescriptions.xml")) {
					if (stream == null) {
						return;
					}

					var ser = new XmlSerializer (typeof (List<TweakInfo>));
					_tweakInfo = (List<TweakInfo>) ser.Deserialize (stream);
				}
			}
			catch {
				return;
			}
		}

		private SkyrimValue<TValue> MakeValue<TValue> (TValue val, string description) {
			var ret = new SkyrimValue<TValue> { Value = val, Description = description };
			PropertyChangedEventManager.AddListener (ret, this, "Value");
			return ret;
		}

		private void MergeIniValues () {
			foreach (var group in _iniValues.GroupBy (i => i.Attribute.Section).Select (g => new { Section = g.Key, Values = g })) {
				CheckSection (group.Section);
				foreach (var value in group.Values) {
					if (value.Type == typeof (bool)) {
						SetIniValue (group.Section, value.Attribute.Name, GetValue<bool> (value.Attribute.Name) ? "1" : "0");
					}
					else if (value.Type == typeof (float)) {
						SetIniValue (group.Section, value.Attribute.Name, GetValue<float> (value.Attribute.Name).ToString ("0.0000"));
					}
					else if (value.Type == typeof (int)) {
						SetIniValue (group.Section, value.Attribute.Name, GetValue<int> (value.Attribute.Name).ToString ());
					}
					else if (value.Type == typeof (uint)) {
						SetIniValue (group.Section, value.Attribute.Name, GetValue<uint> (value.Attribute.Name).ToString ());
					}
					else if (value.Type == typeof (string)) {
						SetIniValue (group.Section, value.Attribute.Name, "\"" + GetValue<string> (value.Attribute.Name) + "\"");
					}
				}
			}
		}

		private void ParseIniValues () {
			foreach (var group in _iniValues.GroupBy (i => i.Attribute.Section).Select (g => new { Section = g.Key, Values = g })) {
				CheckSection (group.Section);
				foreach (var value in group.Values) {
					var rawVal = _iniData[group.Section][value.Attribute.Name];
					if (!String.IsNullOrEmpty (rawVal)) {
						rawVal = rawVal.Trim ();
						switch (value.Type.Name) {
							case "Boolean":
								//value.Property.SetValue (this, (rawVal == "1"), null);
								SetValueContainer (value.Attribute.Name, MakeValue (rawVal == "1", GetDescription (value.Attribute.Name)));
								break;

							case "Single":
								ParseNumeric (rawVal, Single.Parse, value);
								break;

							case "Int32":
								ParseNumeric (rawVal, Int32.Parse, value);
								break;

							case "UInt32":
								ParseNumeric (rawVal, UInt32.Parse, value);
								break;

							case "String":
								SetValueContainer (value.Attribute.Name, MakeValue (rawVal.Replace ("\"", ""), GetDescription (value.Attribute.Name)));
								break;
						}
					}
				}
			}
		}

		private void ParseNumeric<TNum> (string rawVal, Func<string, TNum> parse, IniValueInfo value) {
			try {
				var val = parse (rawVal);
				SetValueContainer (value.Attribute.Name, MakeValue (val, GetDescription (value.Attribute.Name)));
			}
			catch {
				return;
			}
		}

		private void SetIniValue (string group, string name, string val) {
			var section = _iniData[group];
			if (!section.ContainsKey (name)) {
				section.AddKey (name, val);
			}
			else {
				section[name] = val;
			}
		}

		private void SetValueContainer<TValue> (string name, SkyrimValue<TValue> val) {
			_values[name] = val;
			var info = _iniValues.FirstOrDefault (i => i.Attribute.Name == name);
			if (info != null) {
				RaisePropertyChanged (info.Property.Name);
			}
		}

		#endregion

		#region IWeakEventListener Members

		public bool ReceiveWeakEvent (Type managerType, object sender, EventArgs e) {
			if (managerType == typeof (PropertyChangedEventManager) && (e is PropertyChangedEventArgs)) {
				var args = e as PropertyChangedEventArgs;
				if (_values.ContainsValue (sender) && args.PropertyName == "Value") {
					IsDirty = true;
					return true;
				}
			}

			return false;
		}

		#endregion

		#region Nested type: IniValueInfo

		private class IniValueInfo {
			#region Properties

			public IniValueAttribute Attribute { get; set; }
			public PropertyInfo Property { get; set; }
			public Type Type { get; set; }

			#endregion
		}

		#endregion
	}
}
